package de.hsowl.tod4a.global.model;

import java.util.*;

import de.hsowl.tod4a.global.variables.XmlTags;

/**
 * Represents an object with all values and types. NOTE: all values are
 * represented as String and arrays as String[]. Referenced objects are linked
 * by their IDs.
 * 
 * @author Sebastian Witt
 * 
 */
public class ObjectMap {
	public String className;
	public String name;
	public String objectID;

	private HashMap<String, Field> fields = new HashMap<String, Field>();
	private ArrayList<RuntimeField> fieldRuntime = new ArrayList<RuntimeField>();

	public ObjectMap(String className) {
		this.className = className;
	}

	/**
	 * Adds a new Field to this ObjectMap.<br>
	 * A field means e.g. "private int max=23;"
	 * 
	 * @param field
	 * @param type
	 * @param value
	 * @param kind
	 */
	public void addField(String field, String type, String value, String kind) {
		addField_(field, type, value, kind);
	}

	/**
	 * Adds a new Field to this ObjectMap.<br>
	 * A field means e.g. "private int[] max=23;"
	 * 
	 * @param field
	 * @param type
	 * @param value
	 * @param kind
	 */
	public void addField(String field, String type, String[] value, String kind) {
		addField_(field, type, value, kind);
	}

	/**
	 * See {@link ObjectMap#addField(String, String, String[], String)}
	 * 
	 * @param field
	 * @param type
	 * @param value
	 * @param kind
	 */
	public void addField(String field, String type, Object[] value, String kind) {
		String[] arr = new String[value.length];
		int i = 0;
		for (Object o : value) {
			arr[i] = (String) o;
			i++;
		}
		addField_(field, type, arr, kind);
	}

	/**
	 * Adds a new Field to this ObjectMap.<br>
	 * A field means e.g. "private int max=23;"
	 * 
	 * @param field
	 * @param type
	 * @param value
	 * @param kind
	 */
	private void addField_(String field, String type, Object value, String kind) {
		if (fields.containsKey(field)) {
			throw new IllegalArgumentException("Duplicate field: " + field);
		} else {
			fields.put(field, new Field(field, value, type, kind));
		}
	}

	/**
	 * Adds a runtime type to a field and value (object id).
	 * 
	 * @param value
	 * @param runtimeType
	 * @param fieldname
	 */
	public void addRuntimeType(String value, String runtimeType, String fieldname) {
		fieldRuntime.add(new RuntimeField(value, fieldname, runtimeType));
	}

	/**
	 * Returns a corespondending JSON object
	 * 
	 * @return
	 */
	public String toJSON() {
		StringBuffer sb = new StringBuffer();
		sb.append(className +":{");

		int max = fields.values().size();
		int i = 0;
		for (Field field : fields.values()) {
			if (field.value instanceof ObjectMap) {
				ObjectMap map = (ObjectMap) field.value;
				sb.append(map.toJSON());
			} else {
				sb.append(field.name);
				sb.append(":");
				sb.append("\"" + field.value + "\"");
			}
			if (i != (max - 1)) {
				sb.append(",");
			}
			i++;
		}

		sb.append("}");
		return sb.toString();
	}

	public String getClassName() {
		return className;
	}

	public Set<String> getFieldNames() {
		return fields.keySet();
	}

	public Collection<Field> getFields() {
		return fields.values();
	}

	public Field getField(String name) {
		return fields.get(name);
	}

	public String getRuntimeType(String field, String value) {
		for (RuntimeField st : fieldRuntime) {
			if (st.value.equals(value) && st.fieldname.equals(field.toString())) {
				return st.runtimeType;
			}
		}
		return null;
	}

	public String toString() {
		StringBuffer result = new StringBuffer(this.getClass().getCanonicalName() + "[");
		result.append("className=").append(className);
		result.append(",fields={");
		for (Field field : fields.values()) {
			result.append(field.name);
			result.append('(').append(field.type).append(")=");
			result.append(field.value);
			result.append(',');
		}
		String ret = result.subSequence(0, result.length() - 1).toString();
		ret += "}]";
		return ret;
	}

	public String getObjectID() {
		return objectID;
	}

	public void setObjectID(String objectID) {
		this.objectID = objectID;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		if (name == null) {
			name = "NULL";
		}
		this.name = name;
	}

	/**
	 * Compares 2 ObjectsMaps
	 * 
	 * @param compare
	 * @return true->identical; false->different
	 */
	public boolean compareObjectMaps(ObjectMap compare) {
		try {
			// TODO erweitern array objecte
			for (Field field : getFields()) {
				Field compField = compare.getField(field.name);

				if ((field.kind.equals(XmlTags.KIND_PRIMITIVE) && compField.kind.equals(XmlTags.KIND_PRIMITIVE))
						|| (field.kind.equals(XmlTags.KIND_OBJECT) && compField.kind.equals(XmlTags.KIND_OBJECT))) {
					if (field.type.equals(compField.type) == false && field.value.equals(compField.value) == false) {
						return false;
					}
				} else {
					return false;
				}

				if ((field.kind.equals(XmlTags.KIND_ARRAY) && compField.kind.equals(XmlTags.KIND_ARRAY))
						|| (field.kind.equals(XmlTags.KIND_OBJECT_ARRAY) && compField.kind.equals(XmlTags.KIND_OBJECT_ARRAY))) {
					if (field.type.equals(compField.type) == false) {
						return false;
					} else {
						int i = 0;
						for (String value : (String[]) field.value) {
							if (value.equals(((String[]) compField.value)[i]) == false) {
								return false;
							}
							i++;
						}
					}
				} else {
					return false;
				}
			}
			return true;
		} catch (Exception e) {
			return false;
		}
	}
}
